package com.hancher.plugin.hrlog;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;

import com.google.gson.Gson;
import com.hancher.plugin.hrlog.burypoint.BuryPointUtil;
import com.lxj.xpopup.XPopup;
import com.lxj.xpopup.impl.LoadingPopupView;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.activation.CommandMap;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.annotations.Nullable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import io.reactivex.rxjava3.schedulers.Schedulers;

/**
 * 作者：Hancher
 * 时间：2019/1/12.
 * 邮箱：ytu_shaoweijie@163.com
 * 版本：v1.0
 * <point>
 * 说明：
 */
public class HrLogUtil {
    private static final String TAG = "HrLogUtil";

    public final static boolean FORCE = true;

    public final static int VERBOSE = 0x1;
    public final static int INFO = 0x2;
    public final static int DEBUG = 0x4;
    public final static int WARNING = 0x8;
    public final static int ERROR = 0x16;
    public final static int SHOW = INFO | ERROR;

    public final static boolean SYSOUT = false;

    /**
     * 打印调用逻辑，多行显示
     *
     * @param message
     * @return
     */
    private static String formatAllMessage(String message) {
        StackTraceElement[] targetElement = Thread.currentThread().getStackTrace();
        StringBuffer str = new StringBuffer();
        for (int i = 0; i < targetElement.length; i++) {

            String fileName = targetElement[i].getFileName();   //文件名
            String className = targetElement[i].getClassName(); //类名
            int lineNum = targetElement[i].getLineNumber();     //行数
            String methName = targetElement[i].getMethodName(); //方法名

            if ((fileName == null || fileName.length() == 0) || fileName.length() < 5) {
                continue;
            } else {
                str.append(fileName.substring(0, fileName.length() - 4));
            }
            str.append(methName)
                    .append("(): ")
                    .append(message)
                    .append(" (")
                    .append(fileName)
                    .append(":")
                    .append(lineNum)
                    .append(")");
            if (i != targetElement.length - 1) {
                str.append("\n");
            }

        }
        return str.toString();
    }

    /**
     * 格式化文本，格式化成如下模样<br>
     * LogTestActivity.onCreate(): Hello World (LogTestActivity.java:12)
     *
     * @param message
     * @return
     */
    private static String formatMessage(String message, int i) {
        /**
         * i=2 HancherLogUtil.formatMessage()
         * i=3 HancherLogUtil.print()
         * i=4 HancherLogUtil.v()
         * i=5 who call it
         */
        StackTraceElement[] targetElement = Thread.currentThread().getStackTrace();

        String fileName = targetElement[i].getFileName();   //文件名
        String className = targetElement[i].getClassName(); //类名
        int lineNum = targetElement[i].getLineNumber();     //行数
        String methName = targetElement[i].getMethodName(); //方法名

        //严格按(FileName:LineNuber)的格式来写 才可以定位，后面不能加东西的样子
//        String str =
//                fileName.substring(0, fileName.length() - 4) + methName + "(): " +
//                        message +
//                        " (" + fileName + ":" + lineNum + ")";
        String str2 = " (" + fileName + ":" + lineNum + ") "
                + methName + "(): "
                + message;
        return str2;
    }

    static void print(int type, Object... messages) {
        StringBuffer message = new StringBuffer();
        if (messages.length > 1 && (messages[0] instanceof String) && ((String) messages[0]).contains("%")) {
            Object[] messagesTmp = Arrays.copyOfRange(messages, 1, messages.length);
            message.append(String.format((String) messages[0], messagesTmp));
        } else {
            for (Object item : messages) {
                message.append(item);
            }
        }
        if (message.length() > 3000) {
            int i = 0;
            while (i * 3000 < message.length()) {
                String message3;
                if ((i + 1) * 3000 > message.length()) {
                    message3 = formatMessage(message.substring(i * 3000, message.length()), 5);

                } else {
                    message3 = formatMessage(message.substring(i * 3000, (i + 1) * 3000), 5);
                }

                switch (type) {
                    case INFO:
                        Log.i(TAG, message3);
                        break;
                    case DEBUG:
                        Log.d(TAG, message3);
                        break;
                    case WARNING:
                        Log.w(TAG, message3);
                        break;
                    case ERROR:
                        Log.e(TAG, message3);
                        break;
                    default:
                        Log.v(TAG, message3);
                        break;
                }
                if (SYSOUT) {
                    System.out.println(message3);
                }
                if (type != VERBOSE) {
                    writeToFile(type, message3);
                }
                i++;
            }
            return;
        }
        if (FORCE || (SHOW & type) != 0) {
            String formatMessage;
            if (message.toString().startsWith("全埋点")) {
                formatMessage = message.toString();
            } else {
                formatMessage = formatMessage(message.toString(), 5);
            }
            switch (type) {
                case INFO:
                    Log.i(TAG, formatMessage);
                    break;
                case DEBUG:
                    Log.d(TAG, formatMessage);
                    break;
                case WARNING:
                    Log.w(TAG, formatMessage);
                    break;
                case ERROR:
                    Log.e(TAG, formatMessage);
                    break;
                default:
                    Log.v(TAG, formatMessage);
                    break;
            }
            if (SYSOUT) {
                System.out.println(formatMessage);
            }
            if (type != VERBOSE) {
                writeToFile(type, formatMessage);
            }
        }
    }

    public static void v(Object... message) {
        print(VERBOSE, message);
    }

    public static void i() {
        print(INFO, "");
    }

    public static void i(Object... message) {
        print(INFO, message);
    }

    public static void d() {
        print(DEBUG, "");
    }

    public static void d(Object... message) {
        print(DEBUG, message);
    }

    public static void w(Object... message) {
        print(WARNING, message);
    }

    public static void e(Throwable e) {
        print(ERROR, e);
        for (int i = 0; i < e.getStackTrace().length; i++) {
            print(ERROR, e.getStackTrace()[i] + " ");
        }
    }

    public static void e(Object... message) {
        print(ERROR, message);
    }

    public static void e(String message, Throwable e) {
        StringBuffer err = new StringBuffer(message);
        err.append(" ").append(e.getMessage());
        StackTraceElement[] errTrace = e.getStackTrace();
        for (StackTraceElement item : errTrace) {
            err.append("\n").append(item.toString());
        }
        print(ERROR, err.toString());
    }

    public static void intent(Intent intent) {
        if (intent == null) {
            print(DEBUG, "intent null");
            return;
        }
        StringBuffer message = new StringBuffer("intent:");
        message.append("\nflag=").append(intent.getFlags());

        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            message.append("\nbundle=[");
            int i = 0;
            for (String key : bundle.keySet()) {
                message.append(key).append(":").append(bundle.getString(key));
                i++;
                if (i != bundle.size()) {
                    message.append(",");
                }
            }
            message.append("]");
        }
        print(DEBUG, message.toString());
    }

    public static void point(String message) {
        if (FORCE || (SHOW & DEBUG) != 0) {
            String formatMessage = formatAllMessage(message);
            Log.d(TAG, formatMessage);
            writeToFile(VERBOSE, formatMessage);
        }
    }

    public static String obj2JsonStr(Object o) {
        if (o == null) {
            return "null";
        }
        Gson gson = new Gson();
        return gson.toJson(o);
    }

    public static String o(Object obj) {
        if (FORCE || (SHOW & DEBUG) != 0) {
            String formatMessage = obj2JsonStr(obj);
            print(VERBOSE, formatMessage);
            writeToFile(VERBOSE, formatMessage);
            return formatMessage;
        }
        return null;
    }

    public static void map2Json(Object... message) {
        HashMap<String, String> map = new HashMap<>();
        for (int i = 1; i < message.length; i += 2) {
            map.put(message[i].toString(), message[i + 1].toString());
        }
        String newMessage = message[0] + obj2JsonStr(map);
        print(VERBOSE, newMessage);
    }

    private static String logFile = null;
    private static HandlerThread logThread = new HandlerThread("LogHandlerThread");
    private static Handler logHandler = null;
    private static Context mContext = null;
    // 邮箱没空间了 尽量控制在每次50M以下, 500M压缩后大约50M
    public static final int MAX_FILE_COUNT = 10;
    public static final int MAX_FILE_SIZE = 50 * 1024 * 1024;
    private static String SEND_MAIL = "";
    private static String SEND_SMTP = "";
    private static String SEND_PSW = "";

    /**
     * 初始化文件
     *
     * @param context
     */
    public static void initWriteToFile(Application context, String mail, String smtp, String psw) {
        SEND_MAIL = mail;
        SEND_SMTP = smtp;
        SEND_PSW = psw;
        mContext = context.getApplicationContext();
        if (TextUtils.isEmpty(logFile)) {
            if (context == null) {
                v("日志存储 还没初始化就使用文件输出无法继续进行");
                return;
            }
            String parentDir = context.getExternalFilesDir("log").getAbsolutePath();
            File file = new File(parentDir);
            if (!file.exists()) {
                file.mkdirs();
            }

            String logStr = parentDir + File.separator + "hancher" + getAllstr2() + ".log";
            String[] fileNames = new File(logStr).getParentFile().list();
            List<String> newFileNames = Arrays.stream(fileNames).sorted().collect(Collectors.toList());
            String lastElement;
            if (!newFileNames.isEmpty()) {
                lastElement = newFileNames.get(newFileNames.size() - 1);
            } else {
                lastElement = "";
            }
            if (TextUtils.isEmpty(lastElement) || new File(parentDir + File.separator + lastElement).length() > MAX_FILE_SIZE) {//大于  用新的
                logFile = logStr;
                v("日志存储 文件aaa:" + logFile);
            } else {// 用老的
                logFile = parentDir + File.separator + lastElement;
                v("日志存储 文件bbb:" + logFile);
            }

            v("日志存储 文件:" + newFileNames);
            if (newFileNames.size() > MAX_FILE_COUNT) {
                for (int i = 0; i < newFileNames.size() - MAX_FILE_COUNT; i++) {
                    new File(parentDir + File.separator + newFileNames.get(i)).delete();
                    v("日志存储 删除第" + i + "个文件" + newFileNames.get(i));
                }
            }
        }
        if (logThread.isAlive()) {
            try {
                logThread.quit();
            } catch (Exception e) {
                e(e);
            }
        }
        logThread.start();
        logHandler = new Handler(logThread.getLooper()) {

            @Override
            public void handleMessage(Message msg) {
                FileWriter writer = null;
                try {
                    writer = new FileWriter(logFile, true);
                    writer.write(msg.obj + "\n");
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    v("日志存储 关闭输出失败e=" + e);
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                            v("日志存储 关闭输出失败e=" + e);
                        }
                    }
                }
            }
        };
        v("日志存储 初始化成功，开始记录日志：");
        crashProcess();
        recordActivity(context);
    }

    // region 生命周期
    public static void recordActivity(Application context) {
        context.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
                printLifecycle("onCreate", activity);
            }

            @Override
            public void onActivityStarted(@NonNull Activity activity) {
                printLifecycle("onStart", activity);
            }

            @Override
            public void onActivityResumed(@NonNull Activity activity) {
                BuryPointUtil.changeListener(activity);
                printLifecycle("onResume", activity);
            }

            @Override
            public void onActivityPaused(@NonNull Activity activity) {
                printLifecycle("onPause", activity);
            }

            @Override
            public void onActivityStopped(@NonNull Activity activity) {
                printLifecycle("onStop", activity);
            }

            @Override
            public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
                printLifecycle("onSaveInstanceState", activity);
            }

            @Override
            public void onActivityDestroyed(@NonNull Activity activity) {
                printLifecycle("onDestroyed", activity);
            }
        });
    }

    public static void printLifecycle(String lifecycle, Activity activity) {
        if ("onResume".equals(lifecycle)) {
            i("全埋点(生命周期): %s -- %s", activity.getClass().getName(), lifecycle);
        } else {
            v("全埋点(生命周期): %s -- %s", activity.getClass().getName(), lifecycle);
        }
    }
    // endregion

    // region 异常报错拦截log

    /**
     * 异常报错拦截log
     */
    private static void crashProcess() {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
                e("全埋点(异常): " + throwable.getMessage());
                for (int i = 0; i < throwable.getStackTrace().length; i++) {
                    e("全埋点(异常): " + throwable.getStackTrace()[i]);
                }
                android.os.Process.killProcess(android.os.Process.myPid());
                System.exit(1);
            }
        });
    }
    // endregion

    // region 按键监听
    public static void printKeyEvent(KeyEvent event) {
        HashMap<String, String> map = new HashMap<>();
        map.put("event", KeyEvent.keyCodeToString(event.getKeyCode()));
        map.put("code", event.getKeyCode() + "");
        map.put("action", actionToString(event.getAction()));
        HrLogUtil.d("全埋点(按键):", obj2JsonStr(map));
    }

    public static String actionToString(int action) {
        switch (action) {
            case KeyEvent.ACTION_DOWN:
                return "ACTION_DOWN";
            case KeyEvent.ACTION_UP:
                return "ACTION_UP  ";
            case KeyEvent.ACTION_MULTIPLE:
                return "ACTION_MULTIPLE";
            default:
                return Integer.toString(action);
        }
    }
    // endregion

    /**
     * 需要先调用initWriteToFile
     *
     * @param type
     * @param message
     */
    private static void writeToFile(int type, String message) {
        if (logFile == null || logHandler == null) {
            v("日志存储 地址或者子线程异常");
            return;
        }
        try {
            if (new File(logFile).length() > MAX_FILE_SIZE) {
                // 超过10M 重命名
                String parentDir = mContext.getExternalFilesDir("log").getAbsolutePath();
                logFile = parentDir + File.separator + "hancher" + getAllstr2() + ".log";
                // 超过10个文件删除
                String[] fileNames = new File(logFile).getParentFile().list();
                List<String> newFileNames = Arrays.stream(fileNames).sorted().collect(Collectors.toList());
                v("日志存储 文件:" + newFileNames);
                if (newFileNames.size() > MAX_FILE_COUNT) {
                    for (int i = 0; i < newFileNames.size() - MAX_FILE_COUNT; i++) {
                        new File(parentDir + File.separator + newFileNames.get(i)).delete();
                        v("日志存储 删除第" + i + "个文件" + newFileNames.get(i));
                    }
                }
            }
        } catch (Exception e) {
            return;
        }

        if (!TextUtils.isEmpty(message) && message.startsWith("全埋点(异常)")) {
            FileWriter writer = null;
            try {
                writer = new FileWriter(logFile, true);
                writer.write(getAllstr(type) + " " + message + "\n");
                writer.flush();
                writer.close();
            } catch (IOException e) {
                v("日志存储 关闭输出失败e=" + e);
                e.printStackTrace();
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        v("日志存储 关闭输出失败e=" + e);
                    }
                }
            }
            return;
        }
        logHandler.sendMessage(Message.obtain(logHandler, 1, getAllstr(type) + " " + message));
    }

    static SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");

    public static String getAllstr(int type) {
//        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        switch (type) {
            case INFO:
                return simpleDateFormat1.format(new Date()) + " 1111 1111 I";
            case DEBUG:
                return simpleDateFormat1.format(new Date()) + " 1111 1111 D";
            case WARNING:
                return simpleDateFormat1.format(new Date()) + " 1111 1111 W";
            case ERROR:
                return simpleDateFormat1.format(new Date()) + " 1111 1111 E";
            default:
                return simpleDateFormat1.format(new Date()) + " 1111 1111 E";
        }
    }

    static SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS");
    public static String getAllstr2() {
        return simpleDateFormat2.format(new Date());
    }

    // region 上传文件
    public static void uploadLog(Activity context, String msg) {
        String parentDir = context.getExternalFilesDir("log").getAbsolutePath();
        String zipFilePath = context.getExternalFilesDir("").getAbsolutePath() + File.separator + "Android_Log.zip";
        File dir = new File(parentDir);
        String[] files = dir.list();
        v("日志存储 日志文件个数=" + files.length);
        LoadingPopupView waitDialog = (LoadingPopupView) new XPopup.Builder(context)
                .dismissOnBackPressed(false)
                .dismissOnTouchOutside(false)
                .asLoading("正在加载中")
                .show();
        Observable.create((ObservableOnSubscribe<String>) e -> {
                    if (new File(zipFilePath).exists()) {
                        new File(zipFilePath).delete();
                    }
                    zipFiles(parentDir, files, zipFilePath);
                    v("日志存储 压缩成功");
                    boolean result = emailZip(zipFilePath, msg);
                    if (result) {
                        e.onComplete();
                    } else {
                        e.onError(new Exception("上传失败"));
                    }
                    e.onComplete();
                }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(s -> {

                }, e -> {
                    v("日志存储 上传报错e=" + e);
                    waitDialog.dismiss();
                    Toast.makeText(context, "上传失败", Toast.LENGTH_SHORT).show();
                }, () -> {
                    v("日志存储 上传成功");
                    waitDialog.dismiss();
                    Toast.makeText(context, "上传成功", Toast.LENGTH_SHORT).show();
                });

    }

    private static boolean emailZip(String zipFilePath, String userMessage) {
        try {
            //region 发送邮件
            Properties props = new Properties();
            props.put("mail.smtp.host", SEND_SMTP); //从哪个服务器发出去的, key不变
            Session session = Session.getInstance(props, null);
            MimeMessage msg = new MimeMessage(session);
            msg.setFrom(SEND_MAIL);//发送的邮箱账号
            msg.setRecipients(javax.mail.Message.RecipientType.TO, SEND_MAIL);//接受的邮箱账号
            msg.setSubject(userMessage);//标题
            msg.setSentDate(new Date());//时间
            msg.setText("详细查看附件");//内容

            // 添加邮件附件
            MimeMultipart multipart = new MimeMultipart();
            // 添加邮件附件 内容
            MimeBodyPart bodyPart = new MimeBodyPart();
            bodyPart.setContent("详细查看附件", "text/html; charset=utf-8");
            multipart.addBodyPart(bodyPart);
            // 添加邮件附件 附件
            MimeBodyPart attachPart = new MimeBodyPart();
            DataSource source = new FileDataSource(zipFilePath);
            attachPart.setDataHandler(new DataHandler(source));
            attachPart.setFileName(new File(zipFilePath).getName());
            multipart.addBodyPart(attachPart);
            // 添加邮件附件 组合
            msg.setContent(multipart);

            // add handlers for main MIME types
            MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
            mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
            mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
            mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
            mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
            mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
            CommandMap.setDefaultCommandMap(mc);

            //发送邮件
            Transport.send(msg, SEND_MAIL, SEND_PSW);
            //endregion
            return true;
        } catch (Exception e) {
            v(e);
            return false;
        }
    }

    private static boolean zipFiles(String parentDir, String[] files, String zipFile) {
        try (FileOutputStream fos = new FileOutputStream(zipFile);
             ZipOutputStream zos = new ZipOutputStream(fos)) {

            for (String file : files) {
                File f = new File(parentDir, file);
                addFileToZip("", f, zos);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

//
//    public static boolean uploadZipFile(String url, String filePath) {
//        OkHttpClient client = new OkHttpClient();
//
//        File file = new File(filePath);
//        RequestBody requestBody = new MultipartBody.Builder()
//                .setType(MultipartBody.FORM)
//                .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.get("application/zip"), file))
//                .build();
//
//        Request request = new Request.Builder()
//                .url(url)
//                .post(requestBody)
//                .build();
//
//        try (Response response = client.newCall(request).execute()) {
//            return response.isSuccessful();
//        } catch (IOException e) {
//            e.printStackTrace();
//            return false;
//        }
//    }

    private static void addFileToZip(String path, File srcFile, ZipOutputStream zos) throws IOException {
        if (srcFile.isDirectory()) {
            for (File file : srcFile.listFiles()) {
                addFileToZip(path + srcFile.getName() + "/", file, zos);
            }
        } else {
            FileInputStream fis = new FileInputStream(srcFile);
            ZipEntry zipEntry = new ZipEntry(path + srcFile.getName());
            zos.putNextEntry(zipEntry);

            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                zos.write(bytes, 0, length);
            }
            zos.closeEntry();
            fis.close();
        }
    }
    // endregion
}
